﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ThreeDWorld
{
    public class Sprite3DBatch : IDisposable
    {
        private AlphaTestEffect _Effect;
        private VertexPositionTexture[] _Vertices;
        private DynamicVertexBuffer _VertexBuffer;
        private IndexBuffer _IndexBuffer;
        private DepthStencilState _SavedDepthStencilState;
        private BlendState _SavedBlendState;
        private RasterizerState _SavedRasterizerState;
        private Texture2D _LastTexture;

        public Sprite3DBatch(Game game)
        {
            Game = game;

            _Effect = new AlphaTestEffect(GraphicsDevice);
            _Effect.VertexColorEnabled = false;
            _Effect.FogEnabled = false;

            _Vertices = new VertexPositionTexture[4];
            _Vertices[0].TextureCoordinate = new Vector2(0, 1);
            _Vertices[1].TextureCoordinate = new Vector2(0, 0);
            _Vertices[2].TextureCoordinate = new Vector2(1, 0);
            _Vertices[3].TextureCoordinate = new Vector2(1, 1);

            _VertexBuffer = new DynamicVertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 4, BufferUsage.WriteOnly);
            _IndexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
            _IndexBuffer.SetData<short>(new short[] { 0, 1, 2, 2, 3, 0 });
        }

        public Game Game
        {
            get;
            private set;
        }

        public GraphicsDevice GraphicsDevice
        {
            get { return Game.GraphicsDevice; }
        }

        public Camera Camera
        {
            get { return Game.Camera; }
        }

        public void Begin()
        {
            _SavedDepthStencilState = GraphicsDevice.DepthStencilState;
            _SavedBlendState = GraphicsDevice.BlendState;
            _SavedRasterizerState = GraphicsDevice.RasterizerState;

            GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;

            _Effect.World = Matrix.Identity;
            _Effect.View = Camera.View;
            _Effect.Projection = Camera.Projection;

            _LastTexture = null;
        }

        public void End()
        {
            GraphicsDevice.DepthStencilState = _SavedDepthStencilState;
            GraphicsDevice.BlendState = _SavedBlendState;
            GraphicsDevice.RasterizerState = _SavedRasterizerState;
        }

        public void Draw(Texture2D texture, Vector3 position, Vector2 size, Rectangle srcRect, Vector3 color, float alpha, BlendState blendState)
        {
            Vector3 f = Vector3.Normalize(Camera.Target - Camera.Position);
            Vector3 u = Vector3.Normalize(Camera.Up);
            Vector3 r = Vector3.Normalize(Vector3.Cross(f, u));
            Vector2 d = size * 0.5f;
            Vector2 s = new Vector2(texture.Width, texture.Height);

            r *= d.X;
            u *= d.Y;

            if (_LastTexture != texture)
            {
                _Effect.Texture = texture;
                _LastTexture = texture;
            }
            _Effect.DiffuseColor = color;
            _Effect.Alpha = alpha;

            GraphicsDevice.BlendState = blendState;

            _Vertices[0].Position = position - r - u;
            _Vertices[1].Position = position - r + u;
            _Vertices[2].Position = position + r + u;
            _Vertices[3].Position = position + r - u;

            _Vertices[0].TextureCoordinate = new Vector2(srcRect.X, srcRect.Y + srcRect.Height) / s;
            _Vertices[1].TextureCoordinate = new Vector2(srcRect.X, srcRect.Y) / s;
            _Vertices[2].TextureCoordinate = new Vector2(srcRect.X + srcRect.Width, srcRect.Y) / s;
            _Vertices[3].TextureCoordinate = new Vector2(srcRect.X + srcRect.Width, srcRect.Y + srcRect.Height) / s;

            _VertexBuffer.SetData<VertexPositionTexture>(_Vertices);

            try
            {
                GraphicsDevice.SetVertexBuffer(_VertexBuffer);
                GraphicsDevice.Indices = _IndexBuffer;

                foreach (EffectPass pass in _Effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4, 0, 2);
                }
            }
            catch (ObjectDisposedException)
            {
                End();
            }
            finally
            {
                GraphicsDevice.SetVertexBuffer(null);
                GraphicsDevice.Indices = null;
            }
        }

        public void Dispose()
        {
            if (!_VertexBuffer.IsDisposed)
            {
                _VertexBuffer.Dispose();
            }
            if (!_IndexBuffer.IsDisposed)
            {
                _IndexBuffer.Dispose();
            }
        }
    }
}
